/**
 * Copyright (c) 2005-2010 springside.org.cn
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * 
 * $Id: ReflectionUtils.java 1211 2010-09-10 16:20:45Z calvinxiu $
 */
package com.dmagic.cms.common.util.reflection;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;


import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;

/**
 * Reflective tools.
 * 
 * @description : Provide access to private variables to get the generic type
 *              Class to extract the properties of the elements in the collection, convert a string to an object Util function.
 * 
 * @author calvin
 */
public class ReflectionUtil {

	private static Logger logger = LoggerFactory.getLogger(ReflectionUtil.class);

	public static String reflectionToString(final Object object) {
		return (new ReflectionToStringBuilder(object, ToStringStyle.SHORT_PREFIX_STYLE) {
			@Override
			protected boolean accept(Field field) {
				if (Modifier.isStatic(field.getModifiers())) {
					return false;
				}
				String name = field.getName();
				Object value = getFieldValue(object, name);
				return super.accept(field) && value != null;
			}
		}).toString();
	}

	/**
	 * @description : Invoke Getter method
	 * @param obj
	 * @param propertyName
	 * @return
	 */
	public static Object invokeGetterMethod(Object obj, String propertyName) {
		String getterMethodName = "get" + StringUtils.capitalize(propertyName);
		return invokeMethod(obj, getterMethodName, new Class[] {}, new Object[] {});
	}

	/**
	 * @description : Invoke Setter method.Using the value of the Class to Find Setter method.
	 * @param obj
	 * @param propertyName
	 * @param value
	 */
	public static void invokeSetterMethod(Object obj, String propertyName, Object value) {
		invokeSetterMethod(obj, propertyName, value, null);
	}

	/**
	 * @description :Invoke Setter method.
	 * @param obj
	 * @param propertyName
	 * @param value
	 * @param propertyType The Setter method used to find, Class alternative use value is empty.
	 */
	public static void invokeSetterMethod(Object obj, String propertyName, Object value, Class<?> propertyType) {
		Class<?> type = propertyType != null ? propertyType : value.getClass();
		String setterMethodName = "set" + StringUtils.capitalize(propertyName);
		invokeMethod(obj, setterMethodName, new Class[] { type }, new Object[] { value });
	}

	public static Field getFieldByName(Object obj, String name) {
		Field f = null;
		try {
			f = obj.getClass().getDeclaredField(name);
		} catch (Exception e) {
		}
		return f;
	}

	public static boolean isMethodExists(final Object object, final String methodName, final Class<?>[] parameterTypes) {
		Method method = getAccessbleMethod(object, methodName, parameterTypes);
		if (method == null) {
			return false;
		}
		return true;
	}

	public static Method getAccessbleMethod(Object object, String methodName, Class<?>... parameterTypes) {
		Assert.notNull(object);
		for (Class<?> superClass = object.getClass(); superClass != Object.class; superClass = superClass
				.getSuperclass()) {
			try {
				Method method = superClass.getDeclaredMethod(methodName, parameterTypes);
				method.setAccessible(true);
				return method;
			} catch (NoSuchMethodException e) {
			}
		}

		return null;
	}

	public static Method getGetterMethodByPropertyName(Object target, String propertyName) {
		String getterMethodName = "get" + StringUtils.capitalize(propertyName);
		if (!isMethodExists(target, getterMethodName, new Class[] {})) {
			getterMethodName = "is" + StringUtils.capitalize(propertyName);
		}
		Method method = null;
		try {
			method = target.getClass().getDeclaredMethod(getterMethodName, null);
		} catch (Exception e) {
		}
		return method;
	}

	/**
	 * @description : Directly read object attribute values​​, ignoring the private / protected modifiers, and not through the getter function.
	 * @param obj
	 * @param fieldName
	 * @return
	 */
	public static Object getFieldValue(final Object obj, final String fieldName) {
		Field field = getAccessibleField(obj, fieldName);

		if (field == null) {
			throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + obj + "]");
		}

		Object result = null;
		try {
			result = field.get(obj);
		} catch (IllegalAccessException e) {
			logger.error("Impossible to throw exception{}", e.getMessage());
		}
		return result;
	}

	/**
	 * @description : Set the object attribute values ​​directly, ignoring the private / protected modifiers without a setter function.
	 * @param obj
	 * @param fieldName
	 * @param value
	 */
	public static void setFieldValue(final Object obj, final String fieldName, final Object value) {
		Field field = getAccessibleField(obj, fieldName);

		if (field == null) {
			throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + obj + "]");
		}

		try {
			field.set(obj, value);
		} catch (IllegalAccessException e) {
			logger.error("Impossible to throw exception:{}", e.getMessage());
		}
	}

	/**
	 * @description : Cycle upcast get the object DeclaredField, and force set up to access.
	 *             As upcast to the Object still can not be found, return null.
	 * @param obj
	 * @param fieldName
	 * @return
	 */
	public static Field getAccessibleField(final Object obj, final String fieldName) {
		Assert.notNull(obj, "object can not be empty");
		Assert.hasText(fieldName, "fieldName");
		for (Class<?> superClass = obj.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()) {
			try {
				Field field = superClass.getDeclaredField(fieldName);
				field.setAccessible(true);
				return field;
			} catch (NoSuchFieldException e) {// NOSONAR
				// Field not in the current class definition, continue upcast
			}
		}
		return null;
	}

	/**
	 * @description : Directly invoke the object methods, ignoring private / protected modifiers.For a one-time invoke.
	 * @param obj
	 * @param methodName
	 * @param parameterTypes
	 * @param args
	 * @return
	 */
	public static Object invokeMethod(final Object obj, final String methodName, final Class<?>[] parameterTypes,
			final Object[] args) {
		Method method = getAccessibleMethod(obj, methodName, parameterTypes);
		if (method == null) {
			throw new IllegalArgumentException("Could not find method [" + methodName + "] on target [" + obj + "]");
		}

		try {
			return method.invoke(obj, args);
		} catch (Exception e) {
			throw convertReflectionExceptionToUnchecked(e);
		}
	}

	/**
	 * @description :Cycle upcast get the object DeclaredMethod, and force set up to access.
	 *         As upcast to the Object still can not be found, return null.
	 *         Used in the method requires multiple calls first to 
	 *         use this function first obtain the Method, and then call Method.invoke (Object obj, Object ... args)
	 * @param obj
	 * @param methodName
	 * @param parameterTypes
	 * @return
	 */
	public static Method getAccessibleMethod(final Object obj, final String methodName,
			final Class<?>... parameterTypes) {
		Assert.notNull(obj, "object can not be empty");

		for (Class<?> superClass = obj.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()) {
			try {
				Method method = superClass.getDeclaredMethod(methodName, parameterTypes);

				method.setAccessible(true);

				return method;

			} catch (NoSuchMethodException e) {// NOSONAR
				// Method not in the current class definition, continue upcast
			}
		}
		return null;
	}

	/**
	 * @description :Through reflection the generic parameter type of the parent class's Class definition statement.
	 *             If you are not able to find, return Object.class.
	 * eg.
     * public UserDao extends HibernateDao<User>
	 * @param clazz The class to introspect
	 * @return the first generic declaration, or Object.class if cannot be determined
	 */
	@SuppressWarnings("unchecked")
	public static <T> Class<T> getSuperClassGenricType(final Class clazz) {
		return getSuperClassGenricType(clazz, 0);
	}

	/**
	 * 
	 * @description : Through reflection the generic parameter type of the parent class's Class definition statement.
     * If you are not able to find, return Object.class.
     * Such as public UserDao extends HibernateDao <User,Long>
	 * @param clazz clazz The class to introspect
	 * @param index the Index of the generic ddeclaration,start from 0.
	 * @return the index generic declaration, or Object.class if cannot be determined
	 */
	@SuppressWarnings("unchecked")
	public static Class getSuperClassGenricType(final Class clazz, final int index) {

		Type genType = clazz.getGenericSuperclass();

		if (!(genType instanceof ParameterizedType)) {
			logger.warn(clazz.getSimpleName() + "'s superclass not ParameterizedType");
			return Object.class;
		}

		Type[] params = ((ParameterizedType) genType).getActualTypeArguments();

		if (index >= params.length || index < 0) {
			logger.warn("Index: " + index + ", Size of " + clazz.getSimpleName() + "'s Parameterized Type: "
					+ params.length);
			return Object.class;
		}
		if (!(params[index] instanceof Class)) {
			logger.warn(clazz.getSimpleName() + " not set the actual class on superclass generic parameter");
			return Object.class;
		}

		return (Class) params[index];
	}

	/**
	 * @description : Reflection when checked exception into unchecked exception.
	 * @param e
	 * @return
	 */
	public static RuntimeException convertReflectionExceptionToUnchecked(Exception e) {
		if (e instanceof IllegalAccessException || e instanceof IllegalArgumentException
				|| e instanceof NoSuchMethodException) {
			return new IllegalArgumentException("Reflection Exception.", e);
		} else if (e instanceof InvocationTargetException) {
			return new RuntimeException("Reflection Exception.", ((InvocationTargetException) e).getTargetException());
		} else if (e instanceof RuntimeException) {
			return (RuntimeException) e;
		}
		return new RuntimeException("Unexpected Checked Exception.", e);
	}
}
